// 10.3 定制操作
/**
 * 很多算法都会比较输入序列中的元素。默认情况下，这类算法使用元素类型的<或==运算符完成比较。
 * 标准库还为这些算法定义了额外的版本，允许我们提供自己定义的操作来代替默认运算符。
 * 例如，sort算法默认使用元素类型的<运算符。但可能我们希望的排序顺序与<所定义的顺序不同，
 * 或是我们的序列可能保存的是未定义<运算符的元素类型（如Sales_data）。在这两种情况下，都需要重载sort的默认行为。
 */

#include <iterator>
#include <vector>
#include <list>
#include <deque>
#include <forward_list>
#include <string>
#include <array>
#include <stack>
#include <queue>
#include <algorithm>
#include <numeric>
using std::swap;
using std::vector, std::list, std::deque, std::forward_list, std::string, std::array, std::stack, std::queue;
#include "../Chapter07/Sales_data.h"
#include <iostream>
using std::begin, std::cbegin, std::end, std::cend, std::find, std::accumulate, std::equal, std::fill, std::fill_n, std::back_inserter;
using std::cin, std::cout, std::endl;
using std::copy, std::replace, std::replace_copy, std::sort, std::unique, std::stable_sort, std::find_if, std::for_each;
using std::transform;

void elimDups(vector<string> &words);
bool isShorter(const string &s1, const string &s2);
void biggies(vector<string> &words, vector<string>::size_type sz);
string make_plural(std::ptrdiff_t count, string a, string b);
void fcn1();
void fcn2();
void fcn3();
void fcn4();

int main()
{
    // 10.3.3 lambda捕获和返回
    // 当定义一个lambda时，编译器生成一个与lambda对应的新的（未命名的）类类型。
    // 当向一个函数传递一个lambda时，同时定义了一个新类型和该类型的一个对象：传递的参数就是此编译器生成的类类型的未命名对象。
    // 类似的，当使用auto定义一个用lambda初始化的变量时，定义了一个从lambda生成的类型的对象。

    // 值捕获
    // 与参数不同，被捕获的变量的值是在lambda创建时拷贝，而不是调用时拷贝：[插图]
    fcn1();

    // 引用捕获
    // 引用捕获我们定义lambda时可以采用引用方式捕获变量。例如：[插图]、
    fcn2();
    // 引用捕获与返回引用（参见6.3.2节，第201页）有着相同的问题和限制。
    // lambda捕获的都是局部变量，这些变量在函数结束后就不复存在了。如果lambda可能在函数结束后执行，捕获的引用指向的局部变量已经消失。

    // 隐式捕获
    // 为了指示编译器推断捕获列表，应在捕获列表中写一个&或=。
    // &告诉编译器采用捕获引用方式，=则表示采用值捕获方式。例如，我们可以重写传递给find_if的lambda：[插图]
    // auto wc = find_if(words.begin(),words.end(),[=](const string &s){return s.size() >= sz;});

    // 可变lambda
    // 如果我们希望能改变一个被捕获的变量的值，就必须在参数列表首加上关键字mutable。因此，可变lambda能省略参数列表：[插图]
    fcn3();
    // 一个引用捕获的变量是否（如往常一样）可以修改依赖于此引用指向的是一个const类型还是一个非const类型：[插图]
    fcn4();

    // 指定lambda返回类型
    // 默认情况下，如果一个lambda体包含return之外的任何语句，则编译器假定此lambda返回void。
    // 正确示例：
    vector<int> vi{1, -1, 2, -2, 3, -3, 4, 5, 6, 7, 8, 9};
    transform(vi.begin(), vi.end(), vi.begin(), [](int i)
              { return i < 0 ? -i : i; });
    // 错误示例：因为这个lambda体包含了return之外的语句，则编译器假定此lambda返回void。
    transform(vi.begin(), vi.end(), vi.begin(), [](int i)
              { if(i<0) return -i;else return i; });
    // 当我们需要为一个lambda定义返回类型时，必须使用尾置返回类型（参见6.3.3节，第206页）：[插图]
    transform(vi.begin(), vi.end(), vi.begin(), [](int i) -> int
              { if(i<0) return -i;else return i; });
    

    return 0;
}

void elimDups(vector<string> &words)
{
    // 按字典排序words，以便查找重复单词
    sort(words.begin(), words.end());
    // unique重排输入范围，使得每个单词只出现一次
    // 排列在范围的前部，返回指向不重复区域之后一个位置的迭代器
    auto end_unique = unique(words.begin(), words.end());
    // 使用向量操作erase删除重复单词
    words.erase(end_unique, words.end());
}

// 比较函数，用来按长度排序单词
bool isShorter(const string &s1, const string &s2)
{
    return s1.size() < s2.size();
}

// 求大于等于一个给定长度的单词有多少。我们还会修改输出，使程序只打印大于等于给定长度的单词。
void biggies(vector<string> &words, vector<string>::size_type sz)
{
    elimDups(words); // 将words按字典排序并删除重复单词
    // 按长度排序，长度相同的单词维持字典序
    stable_sort(words.begin(), words.end(), isShorter);
    // 获取一个迭代器，指向第一个满足size() > sz的元素
    auto wc = find_if(words.begin(), words.end(), [sz](const string &a)
                      { return a.size() >= sz; });
    // 计算满足size() > sz的元素的数目
    auto count = words.end() - wc;
    cout << count << " " << make_plural(count, "word", "s") << " of length " << sz << " or longer" << endl;
    // 打印长度大于等于给定值的单词，每个单词后面接一个空格
    for_each(wc, words.end(), [](const string &s)
             { cout << s << " "; });
    cout << endl;
}

string make_plural(std::ptrdiff_t count, string a, string b)
{
    if (count > 1)
        return a + b;
    else
        return a;
}

void fcn1()
{
    size_t v1 = 42; // 局部变量
    // 将v1拷贝到名为f的可调用对象
    auto f = [v1]
    { return v1; };
    v1 = 0;
    auto j = f(); // j为42；f保存了我们创建它时v1的拷贝
}

void fcn2()
{
    size_t v1 = 42; // 局部变量
    // 对象f2包含v1的引用
    auto f2 = [&v1]
    { return v1; };
    v1 = 0;
    auto j = f2(); // j为0；f保存了v1的引用，而非拷贝
}

void fcn3()
{
    size_t v1 = 42; // 局部变量
    // f可以改变它所捕获的变量的值
    auto f = [v1]() mutable
    { return ++v1; };
    v1 = 0;
    auto j = f(); // j为43
}

void fcn4()
{
    size_t v1 = 42; // 局部变量
    // v1是一个非const变量的引用
    // 可以通过f2中的引用来改变它
    auto f2 = [&v1]() mutable
    { return ++v1; };
    v1 = 0;
    auto j = f2(); // j为1
}